package com.portal.controller;

import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.portal.constant.RedisConst;
import com.portal.enums.UserStatus;
import com.portal.exception.Assert;
import com.portal.model.entity.BookType;
import com.portal.model.entity.User;
import com.portal.model.vo.LoginRequest;
import com.portal.model.vo.RegisterRequest;
import com.portal.model.vo.UserVo;
import com.portal.result.impl.BusinessCode;
import com.portal.service.BookTypeService;
import com.portal.service.UserService;
import com.portal.util.JwtUtil;
import com.portal.util.SmsUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.List;

import static cn.hutool.jwt.JWTUtil.createToken;


@RestController
@RequestMapping("/user")
@Tag(name = "用户管理")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;
    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private PasswordEncoder passwordEncoder;


    @PostMapping("/login")
    @Operation(summary = "登录认证")
    public ResponseEntity<?> login(@RequestBody LoginRequest param,HttpServletRequest request) {

        // 调用service
        ResponseEntity<?> responseEntity = userService.login(param);

        if (responseEntity.getStatusCode() == HttpStatus.OK) {
            // 获取用户，封装token返回
            User user = (User) responseEntity.getBody();
            return createTokenVo(user,request);
        } else {
            // 登录失败
            return responseEntity;
        }
    }

    /**
     * 问题：用户登录后，后台生成token返回客户端存储； token存储再客户端浏览器！
     * 问题：不安全！ token泄露后就可用访问我们的系统，造成安全隐患！
     * 解决：ip校验方案
     *  1、登录后，用户ip存储到token中
     *  2、每次请求时，校验token： 从token中获取ip，与当前访问者ip进行对比，不一致则拒绝访问！
     */
    private ResponseEntity<?> createTokenVo(User user, HttpServletRequest request) {

        // 封装token数据
        Map<String,Object> claims = new HashMap<>();
        claims.put("userId",user.getId());
        claims.put("userName",user.getAccount());
        claims.put("ip",jwtUtil.getRequestIP(request));

        // 创建token
        String token = jwtUtil.createToken(claims);
        // User对象  ----> UserVO
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user, userVo);

        // token存储一份到Redis中， 格式： user:token:userId xxxooooo  有效时间30min
        stringRedisTemplate.opsForValue().set(RedisConst.USER_TOKEN_PREFIX + user.getId(),
                token, Duration.ofMinutes(300));

        //以响应头的方式发送Token回去
        return ResponseEntity.ok()
                .header("Authorization", token)
                //告知浏览器可以访问 Authorization 响应头，否则访问不了
                .header("Access-Control-Expose-Headers", "Authorization")
                .body(userVo);
    }



    /**
     * 图片验证码
     */
    @GetMapping("/code")
    @Operation(summary = "生成图片验证码")
    public ResponseEntity generateCaptcha() throws IOException {
        //2) 创建缓存图片：指定宽
        int width = 110, height = 45;
        //参数3：图片的类型，使用RGB类型
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        //3) 获取画笔对象
        Graphics graphics = image.getGraphics();
        //4) 设置画笔颜色，并且填充矩形区域
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, width, height);
        //创建一个StringBuider对象
        StringBuilder sb = new StringBuilder();
        //5) 从字符数组中随机得到字符
        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        char[] arr = characters.toCharArray();

        Random random = new Random();

        for (int i = 0; i < 4; i++) {
            //随机从0到长度之间去取索引
            int index = random.nextInt(arr.length);
            //得到1个字符
            char c = arr[index];
            sb.append(c);
            //6) 设置字体，大小为22，设置字的颜色随机
            graphics.setFont(new Font(null, Font.BOLD + Font.ITALIC, 22));
            graphics.setColor(getColor());
            //7) 将每个字符画到图片，位置：5+(i*23), 25
            //将字符转成字符串
            graphics.drawString(String.valueOf(c), 5 + (i * 23), 25);
        }

        //8) 画干扰线8条线，线的位置是随机的，x范围在width之中，y的范围在height之中。
        for (int i = 0; i < 10; i++) {
            graphics.setColor(getColor());
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            graphics.drawLine(x1, y1, x2, y2);
        }
        //9) 将缓存的图片输出到响应输出流中
        //参数1：要输出的缓存图片，参数2：格式，参数3：输出到哪个流中
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "jpeg", outputStream);

        //随机生成一个key，前面加上code::
        String key = RedisConst.USER_VALIDATE_CODE + ":" + UUID.randomUUID().toString().replace("-", "");
        //得到验证码，将生成的字符串放到Redis中，键随机生成，10分钟过期
        stringRedisTemplate.opsForValue().set(key, sb.toString(), Duration.ofMinutes(10));

        log.info("生成的验证码为：{}，Redis中的key是：{}", sb, key);

        Base64.Encoder encoder = Base64.getEncoder();
        String base64Img = "data:image/jpeg;base64," + encoder.encodeToString(outputStream.toByteArray());
        //返回的数据包含验证码的key和图片
        Map<String, String> data = new HashMap<>();
        data.put("key", key);  //redis中键
        data.put("base64Img", base64Img);  //图片
        //返回key和图片的base64的编码
        return ResponseEntity.ok(data);
    }

    //写一个方法随机获取颜色
    private Color getColor() {
        Random random = new Random();
        //红，绿，蓝取值0-255，随机生成 0-255之间数
        int r = random.nextInt(256);
        int g = random.nextInt(256);
        int b = random.nextInt(256);
        return new Color(r, g, b);
    }



    /**
     * 判断用户是否登录
     * 数据从请求头中获取Token数据
     */
    @GetMapping("/isLogged")
    @Operation(summary = "判断用户是否登录")
    public ResponseEntity<?> isLogged(
            @RequestHeader(name = "Authorization") String authorization) {

        // 判断请求头中的token是否为空
        Assert.error(!StringUtils.hasText(authorization), BusinessCode.USER_NOT_LOGGED);

        // 校验token, 返回校验结果
        boolean flag = jwtUtil.validateToken(authorization);
        // 判断验证结果
        Assert.error(!flag, BusinessCode.INVALID_TOKEN);

        // 从token中获取用户信息：userId
        String userId = jwtUtil.getValue(authorization, "userId");
        // 查询返回登录后的用户信息
        UserVo userVo = userService.findById(userId);
        return ResponseEntity.ok(userVo);
    }

    /**
     * 退出
     */
    @DeleteMapping("/logout")
    @Operation(summary = "退出")
    public ResponseEntity<String> logout(
            @RequestHeader("Authorization") String token, HttpServletResponse response) {
        //通过token得到用户id
        String userId = jwtUtil.getValue(token, "userId");
        //删除redis中的token
        stringRedisTemplate.delete(RedisConst.USER_TOKEN_PREFIX + userId);
        //清除缓存
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires", "0");
        return ResponseEntity.ok("退出成功");
    }


    @Autowired
    private SmsUtil smsUtil;

    /**
     * 手机号发短信
     */
    @GetMapping("/sendSms/{telephone}")
    @Operation(summary = "手机号发短信")
    public ResponseEntity<String> sendSms(@PathVariable("telephone") String telephone) {
        //1. 生成四位随机数验证码
        String code = RandomStringUtils.randomNumeric(4);
        log.info("生成的验证码为：{}", code);

        //2. 存储到redis中
        stringRedisTemplate.opsForValue().set(RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone, code, Duration.ofMinutes(10));

        //3. 调用工具包SmsUtil，发送短信
        //smsUtil.sendSms(telephone, code);
        return ResponseEntity.ok("发送验证码成功!");
    }


    /**
     * 验证码登录，手机号不存在就注册
     */
    @PostMapping("/smsLogin")
    @Operation(summary = "发短信登录或注册")
    public ResponseEntity<?> smsLogin(@RequestBody LoginRequest params, HttpServletRequest request) {
        //1. 校验验证码是否正确
        //1.1. 从redis中获取验证码
        String telephone = params.getTelephone();
        String key = RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone;
        String redisCode = stringRedisTemplate.opsForValue().get(key);
        //1.2 判断是否过期
        Assert.error(!StringUtils.hasText(redisCode), BusinessCode.INVALID_VERIFICATION);
        //1.3 判断验证码是否正确
        Assert.error(!redisCode.equals(params.getCode()), BusinessCode.VERIFICATION_ERROR);

        //2. 根据手机号查询，手机号不存在就注册
        User user = userService.getOne(
                Wrappers.<User>lambdaQuery().eq(User::getMobile, telephone));
        //如果用户不存在，创建新用户
        if (user == null) {
            //创建默认密码：123
            String encode = passwordEncoder.encode("123456");
            user = new User();
            user.setMobile(telephone);  //手机号
            user.setPassword(encode);    //加密密码
            user.setAccount(telephone);   //账户名：手机号
            user.setNickname(telephone);
            user.setRealName(telephone);
            user.setState(UserStatus.ACTIVE.getKey());  //状态
            userService.save(user);  // 完成自动注册
        }

        //3. 登录成功，返回UserVo + 响应头携带token
        return createTokenVo(user,request);
    }


    //发件人
    @Value("${spring.mail.username}")
    private String from;

    @Autowired
    private JavaMailSender mailSender;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendEmail")
    @Operation(summary = "发送邮件")
    public ResponseEntity<String> sendEmail(@RequestParam(name = "email") String email) {
        // 记录执行时间
        long start = System.currentTimeMillis();
        log.info("开始执行邮件发送任务...");

        // 1、校验邮箱是否重复
        long count = userService.count(Wrappers.<User>lambdaQuery().eq(User::getEmail, email));
        Assert.error(count > 0, BusinessCode.EMAIL_EXIST_ERROR);

        // 2、发送邮件
        //SimpleMailMessage message = new SimpleMailMessage();
        //message.setSubject("验证码");
        //String code = RandomUtil.randomNumbers(4);
        //message.setText("这是一封测试邮件，请勿回复，验证码是：" + code);
        //message.setFrom(from);
        //message.setTo(email);

        // 优化1：线程优化
        // 后台异步执行？线程
        //new Thread(() -> {
        //    mailSender.send(message);  // 异步发送邮件
        //}).start();

        // 优化2：MQ异步优化
        // 发送消息到队列，消息的内容：邮箱
        Map<String,Object> emailMap = new HashMap<>();
        emailMap.put("from", email);
        emailMap.put("to", email);
        emailMap.put("subject", "验证码");
        String code = RandomUtil.randomNumbers(4);
        emailMap.put("content", "这是一封测试邮件，请勿回复，验证码是：" + code);
        // 发消息到队列，参数1：队列名称； 参数2：消息内容
        // why ？  1、解决高并发问题；  2、系统之间异步处理、解耦； 3、流量削峰； 4、系统之间的通讯，消息的传递
        rabbitTemplate.convertAndSend("email.queue",emailMap);



        log.info("开始执行邮件发送任务...耗时：{}ms", System.currentTimeMillis() - start); //耗时：1045ms  818ms

        // 3、保存验证码到redis中
        stringRedisTemplate.opsForValue().set(RedisConst.EMAIL_ADDRESS_PREFIX + ":" + email, code, Duration.ofMinutes(10));
        return ResponseEntity.ok("发送成功");
    }

    /**
     * 用户注册功能
     */
    @PostMapping("/register")
    @Operation(summary = "用户注册")
    public ResponseEntity<?> register(@RequestBody RegisterRequest params,HttpServletRequest request) {

        // 校验账号唯一
        long count = userService.registCheck("account", params.getAccount());
        Assert.error(count > 0, BusinessCode.USER_IS_EXIST);
        // 校验手机号唯一
        count = userService.registCheck("mobile", params.getMobile());
        Assert.error(count > 0, BusinessCode.MOBILE_IS_EXIST);
        // 校验邮箱唯一
        count = userService.registCheck("email", params.getEmail());
        Assert.error(count > 0, BusinessCode.EMAIL_EXIST_ERROR);

        // 校验验证码
        String redisCode = stringRedisTemplate.opsForValue().get(
                RedisConst.EMAIL_ADDRESS_PREFIX + ":" + params.getEmail());
        Assert.error(redisCode==null || !redisCode.equals(params.getValidCode()),
                BusinessCode.VERIFICATION_ERROR);

        // TODO 删除验证码....

        User user = new User();
        BeanUtils.copyProperties(params, user);
        // 密码加密
        user.setPassword(passwordEncoder.encode(params.getPassword()));
        user.setState(UserStatus.ACTIVE.getKey());
        // 注册
        userService.save(user);

        // 注册后，默认自动登录
        return createTokenVo(user,request);
    }

}













